/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ipc_client_impl_msvc.cpp
 *
 * Purpose: icp client end implementation on windows platform
 *
 * Developer:
 *   wen.gu , 2020-5-15
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#if defined(_MSC_VER)
#include "panda/sys/ipc_client.h"

#include <thread>

#include "ipc_socket_utils_msvc.h"

#pragma comment(lib,"wsock32.lib")

#include "panda/core/utils.h"

#define LOG_TAG "ipcc"
#include "panda//core/log.h"
 
namespace panda
{
namespace sys
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define SOCK_PATH_PREFIX "/tmp/"
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/


class IpcClient::impl
{
    using socket_t = int32_t;

public:
    bool mIsConnected = false;
    bool mIsRunning = true;
    socket_t mSocket = INVALID_SOCKET;
    DataReceiveHandler mReceive;
    std::thread* mThd = nullptr;
    std::string mSrvName;
    std::string mClientAddr;
    ConnectionStateHandler mConnectionStateHandler;

public:
    PandaErrc createSocket(const std::string& srvAddr);

    PandaErrc doConnect(const std::string& srvAddr, uint32_t timeSec=2); /** default connect timeout 2 second */
    PandaErrc startReceive(DataReceiveHandler cb);
    void stopRecive();
    void onReceive();

    void doReconnect();

private:
    void onConnectStateChange(bool connected);

};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/
IpcClient::PandaErrc IpcClient::impl::createSocket(const std::string& srvAddr)
{
    SocketInitializer::get();

    if (mSocket != INVALID_SOCKET)
    {
        LOGE("client socket already exist, please destroy old one before create operation\n");
        return PandaErrc::Undefined;
    }
    if ((mSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    {
        LOGE("create AF_UNIX socket failed\n");
        mSocket = INVALID_SOCKET;
        return PandaErrc::Undefined;
    }

    struct sockaddr_un client_addr;
    memset(&client_addr, 0, sizeof(client_addr));            /* fill socket address structure with our address */
    client_addr.sun_family = AF_UNIX;
    time_t timeTotal = time(NULL);
    struct tm* ptm = localtime(&timeTotal);
    snprintf(client_addr.sun_path, sizeof(struct sockaddr_un), SOCK_PATH_PREFIX "%s_client_%d_%d/%d/%d:%d:%d:%d_%p", 
        srvAddr.c_str(), _getpid(), ptm->tm_year, ptm->tm_mon, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, this);

    int32_t len = offsetof(struct sockaddr_un, sun_path) + strlen(client_addr.sun_path) + 1;
    printf("client addr:%s, len:%d\n", client_addr.sun_path, len);

    int32_t ret = bind(mSocket, (struct sockaddr*) & client_addr, len);
    if (ret < 0)
    {
        LOGE("bind client socket:%s, failed:%ld\n", client_addr.sun_path, WSAGetLastError());
        closesocket(mSocket);
        mSocket = INVALID_SOCKET;
        return PandaErrc::Undefined;
    }

    // set the socket in non-blocking
    unsigned long mode = 1;
    int result = ioctlsocket(mSocket, FIONBIO, &mode);
    if (result != NO_ERROR)
    {
        LOGE("ioctlsocket failed with error: %ld\n", result);
        closesocket(mSocket);
        mSocket = INVALID_SOCKET;
        return PandaErrc::Undefined;
    }

    mClientAddr = client_addr.sun_path;

    return PandaErrc::OK;
}



IpcClient::PandaErrc IpcClient::impl::doConnect(const std::string& srvAddr, uint32_t timeSec /*= 2*/)
{
    if (mIsConnected)
    {
        LOGE("client: %s already to connect to: %s, so disconnect old connect before current operation \n",
             mClientAddr.c_str(), mSrvName.c_str());
        return PandaErrc::Undefined;
    } 

    if (mSocket == INVALID_SOCKET)
    {
        LOGE("client socket not init\n");
        return PandaErrc::NoInit;
    }

    struct sockaddr_un server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    snprintf(server_addr.sun_path, sizeof(struct sockaddr_un), SOCK_PATH_PREFIX "%s", srvAddr.c_str());

    int32_t len = offsetof(struct sockaddr_un, sun_path) + strlen(server_addr.sun_path) + 1;
    ::connect(mSocket, (struct sockaddr*) & server_addr, len);

    fd_set Write, Err;
    FD_ZERO(&Write);
    FD_ZERO(&Err);
    FD_SET(mSocket, &Write);
    FD_SET(mSocket, &Err);

    TIMEVAL timeval = { 0 };
    timeval.tv_sec = timeSec;
    timeval.tv_usec = 0;

    // check if the socket is ready
    int32_t ret = select(0, NULL, &Write, &Err, &timeval);

    if (ret > 0)
    {
        mIsConnected = true;
        mSrvName = srvAddr;
        onConnectStateChange(true);
        return PandaErrc::OK;
    }
    else if (ret == 0)
    {
        /** todo something */  
        return PandaErrc::Timeout;
    }
    else
    {
        LOGE("connect failed with error: %ld\n", WSAGetLastError());
        return PandaErrc::Undefined;
    }
}

void IpcClient::impl::doReconnect()
{
    if (mSocket != INVALID_SOCKET)
    {
        closesocket(mSocket);
        mSocket = INVALID_SOCKET;  /** todo refine me?? */
        mIsConnected = false;

    }

    createSocket(mSrvName);
    doConnect(mSrvName);


}

void IpcClient::impl::onConnectStateChange(bool connected)
{
    if (mConnectionStateHandler)
    {
        mConnectionStateHandler(connected);
    }
    /** todo something */
}

IpcClient::PandaErrc IpcClient::impl::startReceive(DataReceiveHandler cb)
{ 
    mIsRunning = true;
    mReceive = cb;
    mThd = new std::thread(&IpcClient::impl::onReceive, this);

    return PandaErrc::OK;
}

void IpcClient::impl::stopRecive()
{
    if (mThd)
    {
        mIsRunning = false;
        mThd->join();
    }

    if (mSocket != INVALID_SOCKET)
    {
        closesocket(mSocket);
        mSocket = INVALID_SOCKET;
        mIsConnected = false;
    }
}

void IpcClient::impl::onReceive()
{
    uint8_t receiveBuf[2048];
    while (mIsRunning)
    {
        if (mSocket != INVALID_SOCKET)
        {
            int retLen = ::recv(mSocket, (char*)receiveBuf, 2048, 0);

            if (retLen > 0)
            {
                if (mReceive)
                {
                    mReceive(receiveBuf, retLen);
                }
            }
            else
            {
                if (errno != EINTR)
                { /**disconnected between client and service */
                    closesocket(mSocket);
                    mSocket = INVALID_SOCKET;  /** todo refine me?? */
                    mIsConnected = false;
                    onConnectStateChange(false);
                }
            }

        }
        else
        {
            //panda::core::SleepMs(40);
            doReconnect(); /** will max block 2 second */
        }         
    }

}

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
IpcClient::IpcClient(ConnectionStateHandler csh)
    :mImpl(new impl)
{
    mImpl->mConnectionStateHandler = csh;
    /** todo something */
}

IpcClient::~IpcClient()
{
    disconnect();
}

IpcClient::PandaErrc IpcClient::connect(const std::string& srvAddr, DataReceiveHandler cb, int32_t timeOutSec/* = 2*/)
{
    PandaErrc ret = mImpl->createSocket(srvAddr);

    if (bool(ret))
    {
        ret = mImpl->doConnect(srvAddr, timeOutSec);

        if (bool(ret))
        {
            return mImpl->startReceive(cb);
        }
    }

    return ret;

}

void IpcClient::disconnect()
{
    mImpl->stopRecive();
}

IpcClient::PandaErrc IpcClient::sendData(const uint8_t* data, uint32_t size)
{
    if (mImpl->mIsConnected)
    {
        while (size > 0)
        {
            int32_t sendSize = (size >= 1500) ? 1500 : size;   /** is max socket send cache buffer size?? */

            int32_t retSize = ::send(mImpl->mSocket, (char*)data, sendSize, 0);
            if (retSize > 0)
            {
                size -= retSize;
                data += retSize;
            }
            else if (retSize == 0)
            {
                return PandaErrc::Undefined;
            }
            else
            {
                if (errno != EINTR)
                {
                    return PandaErrc::Undefined;
                }
                /** todo something */
            }
        }

        return PandaErrc::OK;
    }

    return PandaErrc::InvalidStatus;
}



} /** namespace sys */
} /** namespace panda */
#endif /** defined(_MSC_VER) */